package com.quantum.eventbus
{

	import com.quantum.collection.DictionarySet;
	import com.quantum.collection.Set;
	import com.quantum.logging.LogFactory;
	import com.quantum.reflection.MetadataObject;
	import com.quantum.reflection.MethodObject;
	import com.quantum.util.Assert;
	import com.quantum.util.ReflectionUtils;
	
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.utils.Dictionary;
	
	import mx.core.FlexGlobals;
	import mx.events.FlexEvent;
	import mx.logging.ILogger;
	import mx.managers.SystemManager;

	public class EventBus extends EventDispatcher
	{
		private static const log:ILogger=LogFactory.getLogger(EventBus);

		private static const METADATA_EVENT:String="Event";
		private static const METADATA_EVENT_ATTRIBUTE_NAME:String="name";
		private static const METADATA_EVENT_ATTRIBUTE_SCOPE:String="scope";
		private static const METADATA_EVENT_ATTRIBUTE_SCOPE_VALUE_PRIVATE:String="private";
		
		private static const METADATA_EVENT_HANDLER:String="EventHandler";
		private static const METADATA_EVENT_HANDLER_ATTRIBUTE_NAME:String="name";
		private static const METADATA_EVENT_HANDLER_ATTRIBUTE_SCOPE:String="scope";
		private static const METADATA_EVENT_HANDLER_ATTRIBUTE_SCOPE_VALUE_PRIVATE:String="private";

		private static var _instance:EventBus;

		public final function addParticipant(participant:Object, useWeakReference:Boolean=false):void
		{
			if (participant == null)
			{
				return;
			}
			var info:ParticipantInfo=_infoByParticipant[participant];
			if (info != null && info.isParticipantWeakReferenced == useWeakReference)
			{
				// the participant is already registered with the same referencing mode
				return;
			}
			if (info != null)
			{
				// The referencing mode is being modified, so we'll re-register the object.
				unregisterAsSubscriber(participant);
//				return;
			}
			else
			{
				info=new ParticipantInfo();
				_infoByParticipant[participant]=info;
				registerAsPublisher(participant);
			}
			info.isParticipantWeakReferenced=useWeakReference;
//			registerAsSubscriber(participant, useWeakReference);
			registerAsSubscriber(participant, useWeakReference);
		}

	
		/**
		 * Unregisters the given object from the event bus so that it will no longer send or
		 * receive events through this bus.
		 *
		 * @param participant
		 *    The object to be unregistered from the event based communication.
		 */
		public final function removeParticipant(participant:Object):void
		{
			if (participant == null)
			{
				return;
			}
			if (_infoByParticipant[participant] == null)
			{
				return;
			}
			delete _infoByParticipant[participant];
			unregisterAsPublisher(participant);
			unregisterAsSubscriber(participant);
			
			// TODO mvdb: A delay of some type is need to avoid error in the EventFunctor
			// This needs to be investigated furthor, so for now we won't call removeRequestor
			// A delay is required, or else an active EventFunctor may fail to find response
			// or request info.
			//_pendingRequestRegistry.removeRequestor( participant );
		}
		
		
		
		private function registerAsPublisher( participant:Object ):void {
			if (! (participant is IEventDispatcher)) 
			{
				return;
			}			
			
			var metadataObjects:Vector.<MetadataObject>= ReflectionUtils.getClassMetadataObjects(participant,METADATA_EVENT);
					
			for each (var metadataObject:MetadataObject in metadataObjects) {					
				var eventName:String = metadataObject.getMetadataArgument(METADATA_EVENT_ATTRIBUTE_NAME);						
				var scope:String = metadataObject.getMetadataArgumentValue(METADATA_EVENT_ATTRIBUTE_SCOPE);
				if (METADATA_EVENT_ATTRIBUTE_SCOPE_VALUE_PRIVATE == scope ) {
					continue;
				}
				participant.addEventListener( eventName, onPublishEvent);
				break;
			}
		}
				
		private function unregisterAsPublisher(participant:Object):void
		{
			if (!(participant is IEventDispatcher))
			{
				return;
			}
			
			var metadataObjects:Vector.<MetadataObject>= ReflectionUtils.getClassMetadataObjects(participant,METADATA_EVENT);
			
			for each (var metadataObject:MetadataObject in metadataObjects) {					
				var eventName:String = metadataObject.getMetadataArgument(METADATA_EVENT_ATTRIBUTE_NAME);
				participant.addEventListener( eventName, onPublishEvent);
			}			
		}
		
		private function registerAsSubscriber(participant:Object, useWeakReference:Boolean, handlerMetadata:String=METADATA_EVENT_HANDLER):void
		{


			var eventHandlerMethods:Array=ReflectionUtils.getMethodObjects(participant, handlerMetadata, "name");
			registerListeners(participant, eventHandlerMethods, useWeakReference, handlerMetadata);

//			// Register response-handler methods.
//			handlerTag=TAG_RESPONSE_HANDLER_KEY;
//			eventHandlerInfos=ReflectionUtil.getTaggedMethods(participant, handlerTag, "name");
//			registerListeners(participant, eventHandlerInfos, useWeakReference, handlerTag);
//			
//			// Register response-handler properties.
//			handlerTag=TAG_RESPONSE_HANDLER_KEY;
//			eventHandlerInfos=ReflectionUtil.getTaggedProperties(participant, handlerTag, "name");
//			registerListeners(participant, eventHandlerInfos, useWeakReference, handlerTag);
		}

		private function registerListeners(participant:Object, eventHandlerMethods:Array, useWeakReference:Boolean, handlerMetadata:String):void
		{

			var metadataObjects:Vector.<MetadataObject>=null;

			var eventName:String=null;
			var scope:String=null;
			for each (var eventHandlerMethod:MethodObject in eventHandlerMethods)
			{
				var memberName:String=eventHandlerMethod.name;
				metadataObjects=eventHandlerMethod.getMetadataObjectByName(handlerMetadata);

				for each (var metadataObject:MetadataObject in metadataObjects)
				{
					scope=metadataObject.getMetadataArgument(METADATA_EVENT_HANDLER_ATTRIBUTE_SCOPE);
//					scope=ReflectionUtil.getFilteredMethodTagValue(participant, memberName, handlerMetadata, "name", eventName, TAGPARAM_SCOPE);

					if (METADATA_EVENT_HANDLER_ATTRIBUTE_SCOPE_VALUE_PRIVATE == scope)
					{
						continue;
					}
//					if (useWeakReference && !isWeakSubscriptionOk(handlerMetadata, scope))
//					{
//						log.info("Handler registration skipped due to weak ref: " + memberName);
//						continue;
//					}

					eventName=metadataObject.getMetadataArgument(METADATA_EVENT_HANDLER_ATTRIBUTE_NAME);

					if (useWeakReference)
					{
						var listener:Function=participant[memberName];
						this.addEventListener(eventName, listener, false, 0, useWeakReference);
					}
					else
					{
						// For the strong reference cases, we have the cability to check for
						// compatibility between type of the raised event and the event type that the
						// listener is expecting. Hence the difference between the way weak
						// referenced participants are addressed, compared to the strong referenced
						// ones.
						addFunctorForParticipant(eventHandlerMethod, participant, handlerMetadata, scope);
					}

				}




			}
		}


		private function addFunctorForParticipant(eventHandlerMethod:MethodObject, participant:Object, handlerTag:String, scope:String):void
		{
			var listener:Function;
			if (evHandlerInfo is TaggedPropertyInfo)
			{
				if (TaggedPropertyInfo(eventHandlerMethod).access == ReflectionUtil.ACCESS_READONLY)
				{
					Assert.assert(false, "Response handler property can't be readonly!");
					return;
				}
			}
			else
			{
				listener=participant[evHandlerInfo.name];
			}
			var eventHandler:EventHandler=new EventHandler();
			eventHandler.target=participant;
			eventHandler.listener=listener;
			eventHandler.handlerInfo=evHandlerInfo;
			eventHandler.isResponseHandler=handlerTag == TAG_RESPONSE_HANDLER_KEY;
			eventHandler.isReflexiveOnly=scope != MODIFIER_SCOPE_PROMISCUOUS;
			eventHandler.pendingRequestRegistry=_pendingRequestRegistry;

			addEventListener(eventHandlerMethod.tagValue as String, eventHandler.onEvent);

			var currentFunctorVal:Object=_functorsByParticipant[participant];
			if (currentFunctorVal == null)
			{
				_functorsByParticipant[participant]=functor;
			}
			else if (currentFunctorVal is Array)
			{
				(currentFunctorVal as Array).push(functor);
			}
			else
			{
				_functorsByParticipant[participant]=[currentFunctorVal, functor];
			}
		}


		
		private function onPublishEvent( event:Event ):void {
			var cloneEvent:Event = event.clone();
//			_originalEventsByClone[cloneEvent] = event;
			this.dispatchEvent( cloneEvent );
		}
		
		
		
		private function unregisterAsSubscriber(participant:Object):void
		{
			if (_functorsByParticipant[participant] != null)
			{
				removeFunctorsForParticipant(participant);
			}
			else
			{
				removeParticipantListenersFromBus(participant);
			}
		}
		
		
		private function removeFunctorsForParticipant(participant:Object):void
		{
			var functorVal:Object=_functorsByParticipant[participant];
			if (functorVal == null)
			{
				return;
			}
			delete _functorsByParticipant[participant];
			var functor:EventHandler;
			if (functorVal is EventHandler)
			{
				functor=EventHandler(functorVal);
				removeEventListener(functor.handlerInfo.tagValue as String, functor.onEvent);
				return;
			}
			if (functorVal is Array)
			{
				for each (functor in(EventHandler as Array))
				{
					removeEventListener(functor.handlerInfo.tagValue as String, functor.onEvent);
				}
			}
		}
		
		/**
		 * Deals with only direct listeners, not EventFunctor`s. Hence it doesn't need to
		 * worry about [ResponseHandler]s as they're always wrapped inside functors.
		 */
		private function removeParticipantListeners(participant:Object):void
		{
			// Only need to process "EventHandler" tag and not "ResponseHandler" because direct
			// subscription (i.e., without an EventHandler) is only for event-handlers.
			var eventHandlerInfos:Array=ReflectionUtil.getTaggedMethods(participant, TAG_EVENT_HANDLER_KEY);
			for each (var evHandlerInfo:TaggedMethodInfo in eventHandlerInfos)
			{
				removeEventListener(evHandlerInfo.tagValue as String, participant[evHandlerInfo.name]);
			}
		}

		
	}
}



/**
 * Information maintained by EventBus for an event messaging participant.
 */
class ParticipantInfo
{
	public var isParticipantWeakReferenced:Boolean=false;

	/**
	 * The number of request events (dispatched by the participant that this info pertains
	 * to) that are still awaiting a response.
	 */
	public var pendingRequestCount:uint=0;
}